<?php 
namespace app\common\support;
/**
 * RSA算法类
 * 签名及密文编码：base64字符串/十六进制字符串/二进制字符串流
 * 填充方式: PKCS1Padding（加解密）/NOPadding（解密）
 *
 * Notice:Only accepts a single block. Block size is equal to the RSA key size! 
 * 如密钥长度为1024 bit，则加密时数据需小于128字节，加上PKCS1Padding本身的11字节信息，所以明文需小于117字节
 *
 */
class RSA{ 
    private $pubKey = null; 
    private $priKey = null; 
    private $public_key_resource = ''; //公密钥资源 
    private $private_key_resource = ''; //私密钥资源

        /**
     * @description: openssl配置文件地址
     * windows中php安装目录下存在此文件，但是php不能自动读取，有两种解决方法，
     *      1、查看phpinfo中openssl.cnf的位置，将openssl.cnf挪到查询到的位置；
     *      2、配置option中的config参数为指定的路径
     * @var array
     */
    private static $_config = 'E:\phpstudy_pro\Extensions\php\php7.1.9nts\extras\ssl/openssl.cnf';

    /**
     * @description: 生成私钥的配置
     * @var array
     */
    private static $_option = [];
    /**
     * 自定义错误处理
    */
    private function _error($msg){ 
    die('RSA Error:' . $msg); //TODO 
    } 
    /**
     * 构造函数
    *
    * @param string 公钥文件（验签和加密时传入）
    * @param string 私钥文件（签名和解密时传入）
    */
    public function __construct($public_key_file = '', $private_key_file = ''){
        $this->pubKey = $public_key_file;   
        $this->priKey = $private_key_file;

        if(false == ($this->public_key_resource = $this->is_bad_public_key($this->pubKey)) || false == ($this->private_key_resource = $this->is_bad_private_key($this->priKey))) { 
            exception('public key or private key no usable');
        }
    }

    private function is_bad_public_key($public_key) { 
        return openssl_pkey_get_public($public_key); 
      } 

    private function is_bad_private_key($private_key) { 
        return openssl_pkey_get_private($private_key); 
      } 
    
      /** 
   * 用公密钥加密 
   */ 
  public function public_encrypt($input) { 
    openssl_public_encrypt($input,$output,$this->public_key_resource); 
    return base64_encode($output); 
  } 

    /** 
   * 解密 公密钥加密后的密文 私钥解
   */ 
  public function private_decrypt($input) { 
    openssl_private_decrypt(base64_decode($input),$output,$this->private_key_resource); 
    return $output; 
  }

    /** 
   * 用私密钥加密 
   */ 
  public function private_encrypt($input) { 
    openssl_private_encrypt($input,$output,$this->private_key_resource); 
    return base64_encode($output); 
  } 
  /** 
   * 解密 私密钥加密后的密文 
   */ 
  public function public_decrypt($input) { 
    openssl_public_decrypt(base64_decode($input),$output,$this->public_key_resource); 
    return $output; 
  } 
    /**
     * 生成签名
    *
    * @param string 签名材料
    * @param string 签名编码（base64/hex/bin）
    * @return 签名值
    */
    public function sign($data, $code = 'base64'){ 
        $ret = false;
        if (openssl_sign($data, $ret, $this->priKey)){ 
            $ret = $this->_encode($ret, $code);
        }
        return $ret; 
    }
    /**
     * 验证签名
    *
    * @param string 签名材料
    * @param string 签名值
    * @param string 签名编码（base64/hex/bin）
    * @return bool 
    */
    public function verify($data, $sign, $code = 'base64'){ 
        $ret = false;
        $sign = $this->_decode($sign, $code);
        if ($sign !== false) {
        switch (openssl_verify($data, $sign, $this->pubKey)){ 
            case 1: $ret = true; break;
            case 0:  
            case -1:  
            default: $ret = false;  
        } 
        } 
        return $ret; 
    } 
    /**
     * 加密
    *
    * @param string 明文
    * @param string 密文编码（base64/hex/bin）
    * @param int 填充方式（貌似php有bug，所以目前仅支持OPENSSL_PKCS1_PADDING）
    * @return string 密文
    */
    public function encrypt($data, $code = 'base64', $padding = OPENSSL_PKCS1_PADDING){
        $ret = false;  
        if (!$this->_checkPadding($padding, 'en')) $this->_error('padding error');
        if (openssl_public_encrypt($data, $result, $this->pubKey, $padding)){
            $ret = $this->_encode($result, $code);
        } 
        return $ret; 
    } 
    /**
     * 解密
    *
    * @param string 密文
    * @param string 密文编码（base64/hex/bin）
    * @param int 填充方式（OPENSSL_PKCS1_PADDING / OPENSSL_NO_PADDING）
    * @param bool 是否翻转明文（When passing Microsoft CryptoAPI-generated RSA cyphertext, revert the bytes in the block）
    * @return string 明文
    */
    public function decrypt($data, $code = 'base64', $padding = OPENSSL_PKCS1_PADDING, $rev = false){
        $ret = false;
        $data = $this->_decode($data, $code);
        if (!$this->_checkPadding($padding, 'de')) $this->_error('padding error');
        if ($data !== false){ 
        if (openssl_private_decrypt($data, $result, $this->priKey, $padding)){
            $ret = $rev ? rtrim(strrev($result), "\0") : ''.$result;
            } 
        } 
        return $ret; 
    } 
    // 私有方法 
    /**
     * 检测填充类型
    * 加密只支持PKCS1_PADDING
    * 解密支持PKCS1_PADDING和NO_PADDING
    * 
    * @param int 填充模式
    * @param string 加密en/解密de
    * @return bool
    */
    private function _checkPadding($padding, $type){
        if ($type == 'en'){ 
        switch ($padding){ 
            case OPENSSL_PKCS1_PADDING: 
            $ret = true; 
            break; 
            default: 
            $ret = false; 
        } 
        } else { 
        switch ($padding){ 
            case OPENSSL_PKCS1_PADDING: 
            case OPENSSL_NO_PADDING: 
            $ret = true; 
            break; 
            default: 
            $ret = false; 
        } 
        } 
        return $ret; 
    } 

    private function _encode($data, $code){ 
        switch (strtolower($code)){ 
        case 'base64': 
            $data = base64_encode(''.$data);
            break; 
        case 'hex': 
            $data = bin2hex($data); 
            break; 
        case 'bin': 
        default: 
        } 
        return $data; 
    } 

    private function _decode($data, $code){ 
        switch (strtolower($code)){ 
        case 'base64': 
            $data = base64_decode($data); 
            break; 
        case 'hex': 
            $data = $this->_hex2bin($data); 
            break; 
        case 'bin': 
        default: 
        } 
        return $data; 
    } 

    /**
     * 获取公钥
     * @param [type] $file
     * @return void
     * @author Sam
     * @date 2022-09-14
     */
    private function _getPublicKey($file){ 
        $key_content = $this->_readFile($file);
        if ($key_content){ 
        $this->public_key_resource = openssl_get_publickey($key_content);
        } 
    } 

    /**
     *获取私钥
     * @param [type] $file
     * @return void
     * @author Sam
     * @date 2022-09-14
     */
    private function _getPrivateKey($file){ 
        $key_content = $this->_readFile($file); 
        if ($key_content){ 
        $this->private_key_resource = openssl_get_privatekey($key_content);
        }
    }

    /**
     * 读取秘钥文件
     * @param [type] $file
     * @return void
     * @author Sam
     * @date 2022-09-14
     */
    private function _readFile($file){
        $ret = false;
        if (!file_exists($file)){
        $this->_error("The file {$file} is not exists");
        } else {
        $ret = file_get_contents($file);
        }
        return $ret;
    } 

        /**
     * @description: RSA秘钥生成配置
     * @return void
     */
    private static function _RSAOption(): void
    {
        static::$_option = [
            'config'           => static::$_config,
            'private_key_type' => OPENSSL_KEYTYPE_RSA, // 秘钥类型
            'private_key_bits' => 2048, // 秘钥位数
        ];
    }

    /**
     * @description: ECC秘钥生成配置
     * @return void
     */
    private static function _ECOption(): void
    {
        static::$_option = [
            'config'           => static::$_config,
            'private_key_type' => OPENSSL_KEYTYPE_EC, // 秘钥类型
            'curve_name'       => 'prime256v1', // 两个广泛的标准化/支持的曲线:prime256v1(NIST P-256)和secp384r1(NIST P-384)
        ];
    }

    /**
     * @description: DH秘钥生成配置,共享秘钥；
     *              通信双方仅通过交换一些可以公开的信息就能够生成出共享的密码数字，而这一密码数字就可以被用作对称密码的密钥
     * @return void
     */
    private static function _DHOption(): void
    {
        static::$_option = [
            'config'           => static::$_config,
            'private_key_type' => OPENSSL_KEYTYPE_DH, // 秘钥类型
            'private_key_bits' => 2048, // 秘钥位数
        ];
    }

    /**
     * @description: DSA秘钥生成配置
     * @return void
     */
    private static function _DSAOption(): void
    {
        static::$_option = [
            'config'           => static::$_config,
            'private_key_type' => OPENSSL_KEYTYPE_DSA, // 秘钥类型
        ];
    }

        /** 
     * 生成一对公私密钥 成功返回 公私密钥数组 失败 返回 false 
     */ 
    public static function create_key() { 
        static::_ECOption();
        $res = openssl_pkey_new(static::$_option); 
        if($res == false) return false; 
        openssl_pkey_export($res, $private_key,'', static::$_option); 
        $public_key = openssl_pkey_get_details($res); 
        return array('public_key'=>$public_key["key"],'private_key'=>$private_key); 
    } 


    private function _hex2bin($hex = false){ 
        $ret = $hex !== false && preg_match('/^[0-9a-fA-F]+$/i', $hex) ? pack("H*", $hex) : false;
        return $ret; 
    } 
}